using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlTypes;
using System.IO;
using System.Text.RegularExpressions;

namespace DotNETX.Utility
{
    public static class StringUtils
    {
        /// <summary>
        /// Determines whether the string contains white space.
        /// </summary>
        /// <param name="s">The string to test for white space.</param>
        /// <returns>
        /// 	<c>true</c> if the string contains white space; otherwise, <c>false</c>.
        /// </returns>
        public static bool ContainsWhiteSpace(string s)
        {
            if (s == null)
                throw new ArgumentNullException("s");

            for (int i = 0; i < s.Length; i++)
            {
                if (char.IsWhiteSpace(s[i]))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Determines whether the string is all white space. Empty string will return false.
        /// </summary>
        /// <param name="s">The string to test whether it is all white space.</param>
        /// <returns>
        /// 	<c>true</c> if the string is all white space; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsWhiteSpace(string s)
        {
            if (s == null)
                throw new ArgumentNullException("s");

            if (s.Length == 0)
                return false;

            for (int i = 0; i < s.Length; i++)
            {
                if (!char.IsWhiteSpace(s[i]))
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Ensures the target string ends with the specified string.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="value">The value.</param>
        /// <returns>The target string with the value string at the end.</returns>
        public static string EnsureEndsWith(string target, string value)
        {
            if (target == null)
                throw new ArgumentNullException("target");

            if (value == null)
                throw new ArgumentNullException("value");

            if (target.Length >= value.Length)
            {
                if (string.Compare(target, target.Length - value.Length, value, 0, value.Length, StringComparison.OrdinalIgnoreCase) ==
                                0)
                    return target;

                string trimmedString = target.TrimEnd(null);

                if (string.Compare(trimmedString, trimmedString.Length - value.Length, value, 0, value.Length,
                                StringComparison.OrdinalIgnoreCase) == 0)
                    return target;
            }

            return target + value;
        }

        /// <summary>
        /// Determines whether the SqlString is null or empty.
        /// </summary>
        /// <param name="s">The string.</param>
        /// <returns>
        /// 	<c>true</c> if the SqlString is null or empty; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNullOrEmpty(SqlString s)
        {
            if (s.IsNull)
                return true;
            else
                return string.IsNullOrEmpty(s.Value);
        }

        /// <summary>
        /// Determines whether the given string is null or empty or whitespace.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <returns>
        /// 	<c>true</c> if the given string is null or empty or whitespace; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNullOrEmptyOrWhiteSpace(string s)
        {
            if (string.IsNullOrEmpty(s))
                return true;
            else if (IsWhiteSpace(s))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Indents the specified string.
        /// </summary>
        /// <param name="s">The string to indent.</param>
        /// <param name="indentation">The number of characters to indent by.</param>
        /// <returns></returns>
        public static string Indent(string s, int indentation)
        {
            return Indent(s, indentation, ' ');
        }

        /// <summary>
        /// Indents the specified string.
        /// </summary>
        /// <param name="s">The string to indent.</param>
        /// <param name="indentation">The number of characters to indent by.</param>
        /// <param name="indentChar">The indent character.</param>
        /// <returns></returns>
        public static string Indent(string s, int indentation, char indentChar)
        {
            if (s == null)
                throw new ArgumentNullException("s");

            if (indentation <= 0)
                throw new ArgumentException("Must be greater than zero.", "indentation");

            StringReader sr = new StringReader(s);
            StringWriter sw = new StringWriter();

            ActionTextReaderLine(sr, sw, delegate(TextWriter tw, string line)
            {
                tw.Write(new string(indentChar, indentation));
                tw.Write(line);
            });

            return sw.ToString();
        }

        private delegate void ActionLine(TextWriter textWriter, string line);

        private static void ActionTextReaderLine(TextReader textReader, TextWriter textWriter, ActionLine lineAction)
        {
            string line;
            bool firstLine = true;
            while ((line = textReader.ReadLine()) != null)
            {
                if (!firstLine)
                    textWriter.WriteLine();
                else
                    firstLine = false;

                lineAction(textWriter, line);
            }
        }

        /// <summary>
        /// Numbers the lines.
        /// </summary>
        /// <param name="s">The string to number.</param>
        /// <returns></returns>
        public static string NumberLines(string s)
        {
            if (s == null)
                throw new ArgumentNullException("s");

            StringReader sr = new StringReader(s);
            StringWriter sw = new StringWriter();

            int lineNumber = 1;

            ActionTextReaderLine(sr, sw, delegate(TextWriter tw, string line)
            {
                tw.Write(lineNumber.ToString().PadLeft(4));
                tw.Write(". ");
                tw.Write(line);

                lineNumber++;
            });

            return sw.ToString();
        }

        /// <summary>
        /// Nulls an empty string.
        /// </summary>
        /// <param name="s">The string.</param>
        /// <returns>Null if the string was null, otherwise the string unchanged.</returns>
        public static string NullEmptyString(string s)
        {
            return (string.IsNullOrEmpty(s)) ? null : s;
        }

        /// <summary>
        /// Replaces the new lines in a string with the given replacement characters.
        /// </summary>
        /// <param name="s">The string to replace new lines in.</param>
        /// <param name="replacement">The replacement characters.</param>
        /// <returns></returns>
        public static string ReplaceNewLines(string s, string replacement)
        {
            StringReader sr = new StringReader(s);
            StringBuilder sb = new StringBuilder();

            bool first = true;

            string line;
            while ((line = sr.ReadLine()) != null)
            {
                if (first)
                    first = false;
                else
                    sb.Append(replacement);

                sb.Append(line);
            }

            return sb.ToString();
        }

        public static string RemoveHtml(string s)
        {
            return RemoveHtmlInternal(s, null);
        }

        public static string RemoveHtml(string s, IList<string> removeTags)
        {
            if (removeTags == null)
                throw new ArgumentNullException("removeTags");

            return RemoveHtmlInternal(s, removeTags);
        }

        private static string RemoveHtmlInternal(string s, IList<string> removeTags)
        {
            List<string> removeTagsUpper = null;

            if (removeTags != null)
            {
                removeTagsUpper = new List<string>(removeTags.Count);

                foreach (string tag in removeTags)
                {
                    removeTagsUpper.Add(tag.ToUpperInvariant());
                }
            }

            Regex anyTag = new Regex(@"<[/]{0,1}\s*(?<tag>\w*)\s*(?<attr>.*?=['""].*?[""'])*?\s*[/]{0,1}>", RegexOptions.Compiled);

            return anyTag.Replace(s, delegate(Match match)
            {
                string tag = match.Groups["tag"].Value.ToUpperInvariant();

                if (removeTagsUpper == null)
                    return string.Empty;
                else if (removeTagsUpper.Contains(tag))
                    return string.Empty;
                else
                    return match.Value;
            });
        }

        /// <summary>
        /// Truncates the specified string.
        /// </summary>
        /// <param name="s">The string to truncate.</param>
        /// <param name="maximumLength">The maximum length of the string before it is truncated.</param>
        /// <returns></returns>
        public static string Truncate(string s, int maximumLength)
        {
            return Truncate(s, maximumLength, "...");
        }

        /// <summary>
        /// Truncates the specified string.
        /// </summary>
        /// <param name="s">The string to truncate.</param>
        /// <param name="maximumLength">The maximum length of the string before it is truncated.</param>
        /// <param name="suffix">The suffix to place at the end of the truncated string.</param>
        /// <returns></returns>
        public static string Truncate(string s, int maximumLength, string suffix)
        {
            if (suffix == null)
                throw new ArgumentNullException("suffix");

            if (maximumLength <= 0)
                throw new ArgumentException("Maximum length must be greater than zero.", "maximumLength");

            int subStringLength = maximumLength - suffix.Length;

            if (subStringLength <= 0)
                throw new ArgumentException("Length of suffix string is greater or equal to maximumLength");

            if (s != null && s.Length > maximumLength)
            {
                string truncatedString = s.Substring(0, subStringLength);
                // incase the last character is a space
                truncatedString = truncatedString.TrimEnd();
                truncatedString += suffix;

                return truncatedString;
            }
            else
            {
                return s;
            }
        }

        /// <summary>
        /// Creates a StringWriter with the specified capacity.
        /// </summary>
        /// <param name="capacity">The capacity of the StringWriter.</param>
        /// <returns></returns>
        public static StringWriter CreateStringWriter(int capacity)
        {
            StringBuilder sb = new StringBuilder(capacity);
            StringWriter sw = new StringWriter(sb);

            return sw;
        }

        /// <summary>
        /// Gets the length of a string, returning null if the string is null.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static int? GetLength(string value)
        {
            if (value == null)
                return null;
            else
                return value.Length;
        }

        /// <summary>
        /// Returns the specified char's unicode string.
        /// </summary>
        /// <param name="c">The c.</param>
        /// <returns></returns>
        public static string ToCharAsUnicode(char c)
        {
            using (StringWriter w = new StringWriter())
            {
                WriteCharAsUnicode(w, c);
                return w.ToString();
            }
        }

        /// <summary>
        /// Writes the specified char's unicode string to a TextWriter.
        /// </summary>
        /// <param name="writer">The writer.</param>
        /// <param name="c">The c.</param>
        public static void WriteCharAsUnicode(TextWriter writer, char c)
        {
            if (writer == null) return;

            char h1 = NumberUtils.IntToHex((c >> 12) & '\x000f');
            char h2 = NumberUtils.IntToHex((c >> 8) & '\x000f');
            char h3 = NumberUtils.IntToHex((c >> 4) & '\x000f');
            char h4 = NumberUtils.IntToHex(c & '\x000f');

            writer.Write('\\');
            writer.Write('u');
            writer.Write(h1);
            writer.Write(h2);
            writer.Write(h3);
            writer.Write(h4);
        }
    }
}